/* -*- c -*-
 * Originally from work by folkert@vanheusden.com and Josef Matondang
 * (https://github.com/josefmtd/kiss-lora)
 */

// TODO: remove sscanf
// - repeat only if included in list or WIDE
// CHARGEN bug when giving path
// VK2CJB->FL0RA-1,WIDE1-1:CHARGEN
// "STATUS"?
// Yes, the <stdio.h> things are bloating to all hell
// sscanf in particular needs to go

#include <stdio.h>
#include <SPI.h>
#include <EEPROM.h>
#include "RH_RF95.h"

#define REPEATERCALL "FL0RA"
#define REPEATERSSID 2
#define MAXPACKETSIZE 251
#define pinreset 9
#define DEFAULTWAIT 60

const uint8_t wide1str[7] = "\xAE\x92\x88\x8A\x62@"; // "WIDE1 " (w/o ssid)
const uint8_t wide2str[7] = "\xAE\x92\x88\x8A\x64@"; // "WIDE2 "
uint8_t ident[] = "\x00@@@@@@@@@@@@@@\x03\xf0T#1,pp,REBOOT                                     ";
uint8_t buffer[MAXPACKETSIZE+1];
uint8_t ourpath[7];
uint32_t rptcnt = 0, nextident = 0;
uint16_t identinterval = DEFAULTWAIT, replydelay = 250;
uint8_t nbytes = MAXPACKETSIZE;
uint8_t powerlevel = 7, reboots = 0, repeatall = 0, mirror = 0;
uint8_t maxpower = 15;		/* in dbm .. 20/~100mW would be >LIPD */

static RH_RF95 rf95 (10, 2);

// ----------------------------------------------------------------------
// All that remains of the previous guy's code...

bool peekRadio (void)
{
  return rf95.available();
}


void getRadio (uint8_t *const recv_buffer,
	       uint8_t *const recv_buffer_size)
{
/* grr.. used to be *recv_buffer_size and taken as an input,
   which created a long-time bug if nbytes wasn't munged before
   calling..*/
  uint8_t dummy = MAXPACKETSIZE;

  while (rf95.recv (recv_buffer, &dummy) == false)
    continue;

  *recv_buffer_size = dummy;
}


void putRadio (const uint8_t *const send_buffer,
	       const uint8_t send_buffer_size)
{
  rf95.setTxPower (powerlevel);

  while (rf95.send (send_buffer, send_buffer_size) == false)
    continue;

  rf95.waitPacketSent();
}


bool initRadio (void)
{
  if (rf95.init())
    {
      delay (100);
      return true;
    }
  return false;
}


bool resetRadio (void)
{
  digitalWrite (pinreset, LOW);
  delay (1);
  digitalWrite (pinreset, HIGH);
  delay (5+1); // Must wait 5 ms before init
  return initRadio();
}

// ----------------------------------------------------------------------

uint8_t mystrlen (uint8_t *p)
{
  uint8_t r = 0;
  while (*p++) r++;
  return r;
}


void mymemset (uint8_t *p, uint8_t b, uint8_t c)
{
  while (c--) *p++ = b;
}


int mymemcmp(const void *s1, const void *s2, int n)
{
    uint8_t u1, u2;

    for (; n--; s1++, s2++)
      {
	u1 = *(uint8_t*) s1;
	u2 = *(uint8_t*) s2;
	if (u1 != u2)
	  return (u1-u2);
      }
    return 0;
}

void gen_call (char* dst, const char* call, int ssid, int chflag, int endflag)
{
  uint8_t i, j;

  for (j = 0; call[j]; j++)
    dst[j] = call[j] << 1;
  while (j < 6)
    dst[j++] = 64;
  dst[6] = (chflag ? 128 : 0) + 64 + 32 + ((ssid&15) << 1) + (endflag&1);
}


void addaddress (uint8_t s, char* rawcallsign)
{
  nbytes += 7;
  if (nbytes > MAXPACKETSIZE)
    {
      // if too large a packet, invalidate
      mymemset (buffer, 0, 15);
      nbytes -= 7;
      return;
    }

  for (uint8_t i = nbytes; i >= s; i--)
    buffer[i] = buffer[i-7];

  memcpy (buffer+s, rawcallsign, 7);
  buffer[s-1] &= 254; /* remove the end-of-list bit from the prev addr */
}


void removeaddress (uint8_t s)
{
  for (; s < nbytes-7; s++)
    buffer[s] = buffer[s+7];
  nbytes -= 7;
}


uint32_t readVcc (void)
{
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay (10); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert

  while (bit_is_set(ADCSRA,ADSC)) continue;

  uint32_t result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}

// ----------------------------------------------------------------------
// ----------------------------------------------------------------------

void update_eeprom (void)
{
  EEPROM.write (0, powerlevel); EEPROM.write (1, 255-powerlevel);
  EEPROM.write (2, reboots); EEPROM.write (3, 255-reboots);
  EEPROM.write (4, maxpower); EEPROM.write (5, 255-maxpower);
  EEPROM.write (6, repeatall); EEPROM.write (7, 255-repeatall);
  EEPROM.write (8, mirror); EEPROM.write (9, 255-mirror);
}

void setup (void)
{
  // quick hack - if the system is browning-out on tx and resetting,
  // just wait a bit before repeating the cycle .. otherwise it'll
  // constantly pip at 1 Hz and wedge people's garage door openers..
  // 20210617 - pip the LED to indicate the situtation

  pinMode (13, OUTPUT);
  for (uint8_t i = DEFAULTWAIT >> 1; i; i--)
    {
      digitalWrite (13, HIGH);
      delay (2);
      digitalWrite (13, LOW);
      delay (1998);
    }

  pinMode (pinreset, OUTPUT);
  digitalWrite (pinreset, HIGH);

  while (!resetRadio())
    continue;

  mirror = EEPROM.read(8);
  if ((mirror > 1) || (mirror != (255-EEPROM.read(9))))
    mirror = 0;

  repeatall = EEPROM.read(6);
  if ((repeatall > 1) || (repeatall != (255-EEPROM.read(7))))
    repeatall = 0;

  repeatall = 0;		/* the world is not ready... */
  mirror = 0;

  maxpower = EEPROM.read(4);
  if ((maxpower > 15) || (maxpower != (255-EEPROM.read(5))))
    maxpower = 15;

  powerlevel = EEPROM.read(0);
  if ((powerlevel > maxpower) || (powerlevel != (255-EEPROM.read(1))))
    powerlevel = 0;

  reboots = EEPROM.read(2);
  if (reboots != (255-EEPROM.read(3)))
    reboots = 0;

  if (++reboots > 3)
    {
      powerlevel >>= 1;
      reboots = 0;
    }

  update_eeprom();

  gen_call (ourpath, REPEATERCALL, REPEATERSSID, 0, 1);
  memcpy (ident+8, ourpath, 7);
  gen_call (ident+1, "BEACON", 0, 0, 0);
  sprintf (ident+21, "%02u", powerlevel);
  ident[23] = ','; ident[30] = 0;
  putRadio (ident, 21+mystrlen(ident+21));
  delay (1000);
}

// ----------------------------------------------------------------------
// ----------------------------------------------------------------------

void loop (void)
{
  uint32_t now = millis();

  // Ident transmission

  if ((identinterval == 0) || (identinterval > 600))
    identinterval == 60;
  if ((replydelay < 10) || (replydelay > 1000))
    replydelay = 50;

  if ((now > 0) && (now >= nextident))
    {
      nextident = now + (identinterval * 1000L);
      int hours = now / 3600000L;
      now -= hours * 3600000L;
      sprintf (ident+21, "%02u,%04u", powerlevel, readVcc());
      sprintf (ident+28, ",%u,%u,vk2cjb@gmail.com", hours, now / 60000L);
      putRadio (ident, mystrlen (ident+21)+21);

      if (powerlevel < maxpower)
	{
	  powerlevel++;
	  reboots = 0;
	  update_eeprom();
	}
      else
	powerlevel = maxpower;

      return;
    }

  // ----------------------------------------------------------------------

  if (!peekRadio())
    return;

  mymemset (buffer, 0, MAXPACKETSIZE);
  getRadio (buffer, &nbytes);
  if (nbytes < 17)
    return;

  nextident = now + (identinterval * 1000L); /* more of a timeout.. */
  delay (replydelay);

  if (powerlevel == 0)
    {
      delay (DEFAULTWAIT*500);
      return;
    }

  // ----------------------------------------------------------------------
  // Addressed to this repeater - possible command

  uint8_t i;

  if (mymemcmp (buffer+1, ourpath, 6) == 0 &&
      ((buffer[7] >> 1) & 15) == REPEATERSSID)
    {
      buffer[0] = 0;
      memcpy (buffer+1, buffer+8, 7);
      buffer[7] &= 254;
      memcpy (buffer+8, ourpath, 7);
      buffer[15] = 0x3;
      buffer[16] = 0xf0;

      //

      if (mymemcmp (buffer+17, "SET ", 4) == 0)
	{
	  int t=-1, d=-1, p=-1;
	  buffer[nbytes] = 0;	/* because str */
	  if (sscanf (buffer+17, "SET %d,%d,%d", &t, &d, &p) > 0)
	    {
	      nextident = 0;
	      if (t > -1) identinterval = t;
	      if (d > -1) replydelay = d;
	      if (p > -1) { powerlevel = p; maxpower = p; update_eeprom(); }
	    }
	  sprintf (buffer+17, "identinterval=%u replydelay=%u maxpower=%u",
		   identinterval, replydelay, maxpower);
	}
      else

	if (mymemcmp (buffer+17, "CHARGEN", 7) == 0)
	  {
	    for (i = 21; i < MAXPACKETSIZE; i++)
	      {
		sprintf (buffer+17, "%03d", i+1);
		buffer[20] = 32; /* fixes the \0 */
		buffer[i] = ((i+73) % 94) + 33;
		putRadio (buffer, i+1);
		if (peekRadio()) /* abort on any traffic */
		  i = MAXPACKETSIZE;
		delay (1000);
	      }
	    return;
	  }
	else

	  if (mymemcmp (buffer+17, "RESET", 5) == 0)
	    {
	      sprintf (buffer+17, "Rebooting...");
	      putRadio (buffer, 17+mystrlen(buffer+17));
	      delay (1000);
	      asm volatile ( "jmp 0");
	    }
	  else

	    if (mymemcmp (buffer+17, "SPAMMODE", 8) == 0)
	      {
		repeatall = (buffer[26] == '1') ? 1 : 0;
		//eeprom_update();
		sprintf (buffer+17, "Repeat all normally-dropped packets: %s",
			 (repeatall ? "ON" : "OFF"));
	      }
	    else

	    if (mymemcmp (buffer+17, "MIRROR", 6) == 0)
	      {
		mirror = (buffer[24] == '1') ? 1 : 0;
		//eeprom_update();
		sprintf (buffer+17, "Transparently retransmit: %s", (mirror ? "ON" : "OFF"));
	      }
	    else

	      /* default response */
	      {
		if (buffer[18] == 'I')
		  buffer[18] = 'O';
		if (nbytes < (MAXPACKETSIZE-40))
		  {
		      sprintf (buffer+nbytes, " RSSI=%d SNR=%d eHz=%d PktRpts=%u",
			       rf95.lastRssi(), rf95.lastSNR(),
			       rf95.frequencyError(), rptcnt);
		    }
		}

      // strlen safe to use here (no null characters)
      putRadio (buffer, 17+mystrlen(buffer+17));
      return;
    }

  // ----------------------------------------------------------------------
  // obvious problems if there's more than 1 digi with this on..

  if (mirror)
    {
      putRadio (buffer, nbytes);
      return;
    }

#if 0
  // check that it's a valid AX.25 packet
  // note the 0xc0's don't appear in buffer[]
  uint8_t isax25 = 0;

  // need to check dest,source,addresses are valid
  // check buffer[0] is valid (and to channel 0)
  int i;

  for (i = 1; (i < 58) && ((buffer[i] & 1) == 0); i++)
    continue;
  if ((buffer[0] == 0) && (i < 58) && (buffer[i] == 0x03) &&
      (buffer[i+1] == 0xf0))
    isax25 = 1;

  if (!isax25)
    {
      putRadio (buffer, nbytes);
      return;
    }
#endif

  // ----------------------------------------------------------------------

  i = 15;
  uint8_t foundwide1 = 0, dorepeat = 0;

  while (!(buffer[i-1] & 1))
    {
      uint8_t ssid = (buffer[i+6] >> 1) & 15;
      uint8_t endoflist = buffer[i+6] & 1;

      // check own callsign/address in path
      if ((mymemcmp (buffer+i, ourpath, 6) == 0) && (ssid == REPEATERSSID))
	{
	  dorepeat++;
	  // if H-set, and ssid matches, it's a loopback, so drop packet
	  if (buffer[i+6] & 128) // H flag
	    return;
	  // if H-clear, remove from pathlist (it'll be added back later)
	  removeaddress(i);
	  buffer[i-1] |= endoflist; /* if was end, give bit to prev addr */
	  continue;
	}

      // check WIDE1
      if (mymemcmp (buffer+i, wide1str, 6) == 0)
	{
	  foundwide1++;
	  dorepeat++;
	  removeaddress(i);
	  buffer[i-1] |= endoflist; /* if was end, give bit to prev addr */
	  continue;
	}

      // check WIDE2
      if (!foundwide1 && (mymemcmp (buffer+i, wide2str, 6) == 0))
	{
	  dorepeat++;
	  // remove from pathlist if ssid=1
	  if (ssid < 2)
	    {
	      removeaddress(i);
	      buffer[i-1] |= endoflist;
	      continue;
	    }
	  else
	    // decrease ssid
	    buffer[i+6] -= 2;
	}

      // erasing any prior end-bit
      buffer[i-1] &= 254;
      i += 7;
    }

  if (i >= 56)		/* drop over-travelling packets */
    return;

  // add our H-setted path
  addaddress (i, ourpath);
  buffer[i+6] |= 128;		/* add H flag */

  if (dorepeat || repeatall)
    {
      putRadio (buffer, nbytes);
      rptcnt++;
    }
}

// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
